import base64
import binascii
import hashlib
import hmac
import os
import random
import string
import time
from io import BytesIO
from time import time
from urllib.parse import urlencode, urljoin

import requests
from flask import Blueprint, current_app, jsonify, make_response
from flask_restful import Resource, reqparse
from werkzeug.datastructures import FileStorage
from werkzeug.utils import secure_filename

from app.extensions import api, redis

api_route = Blueprint('api', __name__)


class Auth:
    def __init__(self, app_id, secret_id, secret_key):
        self._app_id, self._secret_id, self._secret_key = str(app_id), str(secret_id), str(secret_key)

    def get_sign(self, bucket, expire=30):
        """ GET REUSABLE SIGN
        :param bucket: 图片处理所使用的 bucket
        :param expire: 签名的有效时长，单位 秒
        :return: 签名字符串
        """
        now = int(time())
        rdm = random.randint(0, 999999999)

        text = 'a=' + self._app_id + '&b=' + bucket + '&k=' + self._secret_id + '&e=' + str(now + expire) + \
               '&t=' + str(now) + '&r=' + str(rdm) + '&f='
        hex_string = hmac.new(self._secret_key.encode('utf-8'), text.encode('utf-8'), hashlib.sha1).hexdigest()
        bin_string = binascii.unhexlify(hex_string)

        return base64.b64encode(bin_string + text.encode('utf-8')).rstrip()


def allowed_file(filename):
    """ 文件类型检查 """
    return '.' in filename and filename.rsplit('.', 1)[1] in current_app.config['ALLOWED_EXTENSIONS']


def get_file_md5(f):
    """ 获取文件MD5 """
    hash_md5 = hashlib.md5()
    for chunk in iter(lambda: f.read(4096), b''):
        hash_md5.update(chunk)
    f.seek(0)
    return hash_md5.hexdigest()


parser = reqparse.RequestParser()
parser.add_argument('merge_file', type=FileStorage, location='files', required=True)  # 融合文件


class UploadImageApi(Resource):
    """ 上传图片API """

    @staticmethod
    def post():
        args = parser.parse_args()

        merge_file = args.get('merge_file')
        filename = secure_filename(merge_file.filename)
        if merge_file and allowed_file(filename):
            # 获取融合图MD5
            merge_md5 = get_file_md5(merge_file)

            # 腾讯云API鉴权
            auth = Auth(current_app.config['APP_ID'],
                        current_app.config['SECRET_ID'],
                        current_app.config['SECRET_KEY'])
            sig = auth.get_sign('', 1200)

            # 获取人脸检测结果
            url = 'http://recognition.image.myqcloud.com/face/detect'
            detect_res = requests.post(url,
                                       data=dict(appid=current_app.config['APP_ID'],
                                                 mode=1),
                                       files=dict(image=merge_file),
                                       headers={'Authorization': sig})
            if detect_res.status_code == 200:
                detect_res = detect_res.json()
                if detect_res.get('code') == 0:
                    try:
                        gender = detect_res['data']['face'][0]['gender']
                        gender = int(round(gender / 100))
                    except (KeyError, IndexError):
                        gender = None
                    # 存储文件到本地
                    merge_file.seek(0)
                    merge_file.save(os.path.join(current_app.config['IMG_UPLOAD_FOLDER'], merge_md5 + '.jpg'))

                    # 缓存图片信息
                    redis.set(merge_md5, merge_md5 + '.jpg')
                    redis.expire(merge_md5, 1200)
                    return jsonify(merge_id=merge_md5, gender=gender)
                return jsonify(error=1002, message='检测不到人脸')
        return jsonify(error=1001, message='上传图片异常')


parser2 = reqparse.RequestParser()
parser2.add_argument('merge_id', type=str)  # 融合图MD5
parser2.add_argument('model_id', type=str, required=True)  # 模板图ID
parser2.add_argument('image_id', type=str)  # CDN图片文件名


class MergeFaceApi(Resource):
    """ 人脸融合API """

    @staticmethod
    def post():
        args = parser2.parse_args()

        merge_id = args.get('merge_id')
        model_id = args.get('model_id')
        image_id = args.get('image_id')

        # 获取图片
        if image_id:
            image_url = urljoin(current_app.config['CDN_URL'], image_id)
            res = requests.get(image_url)
            if res.status_code == 200:
                payload = res.content
                buffered = BytesIO(payload)
                merge_b64 = base64.b64encode(buffered.getvalue())
            else:
                return jsonify(error=2003, message='CDN资源不存在')
        elif merge_id:
            merge_path = redis.get(merge_id) or ''
            merge_path = os.path.join(current_app.config['IMG_UPLOAD_FOLDER'], merge_path)
            if os.path.isfile(merge_path):
                merge_file = open(merge_path, 'rb')
                merge_b64 = base64.b64encode(merge_file.read())
            else:
                return jsonify(error=2004, message='本地资源不存在')
        else:
            return jsonify(error=2001, message='融合图片不存在')

        # 腾讯云API鉴权
        auth = Auth(current_app.config['APP_ID'],
                    current_app.config['SECRET_ID'],
                    current_app.config['SECRET_KEY'])
        sig = auth.get_sign('', 1200)

        # 获取融合结果
        url = 'http://aiconsole.cloud.tencent.com/fuseapi/face'
        res = requests.post(url, json=dict(appid=current_app.config['APP_ID'],
                                           uin=current_app.config['UIN'],
                                           project_id=current_app.config['PROJECT_ID'],
                                           model_id=model_id,
                                           img_data=merge_b64.decode('utf-8'),
                                           rsp_img_type='base64'), headers={'Authorization': sig})
        if res.status_code == 200:
            res = res.json()
            if res.get('ret') == '0':
                return jsonify(result=res.get('img_base64'))
        return jsonify(error=2002, message='融合请求失败')


parser3 = reqparse.RequestParser()
parser3.add_argument('filter', type=int, required=True)
parser3.add_argument('image', type=FileStorage, location='files', required=True)


class TestApi(Resource):
    @staticmethod
    def post():
        args = parser3.parse_args()
        image = args.get('image')
        fil = args.get('filter')

        app_id = 2107999572
        app_key = 'sQZae4iQPgXu6haw'
        time_stamp = int(time())
        nonce_str = ''.join(random.sample(string.ascii_letters + string.digits, 16))
        params = dict(app_id=app_id,
                      time_stamp=time_stamp,
                      nonce_str=nonce_str,
                      filter=fil,
                      image=image)
        params_sorted = sorted([(k, v) for k, v in params.items()], key=lambda x: x[0])
        params_sorted.append(('app_key', app_key))
        sign = urlencode(params_sorted)
        sign = hashlib.md5(sign.encode()).hexdigest().upper()
        url = 'https://api.ai.qq.com/fcgi-bin/ptu/ptu_imgfilter'

        res = requests.post(url, data=dict(app_id=app_id,
                                           time_stamp=time_stamp,
                                           nonce_str=nonce_str,
                                           sign=sign,
                                           filter=fil,
                                           image=image))
        if res.status_code == 200:
            res = res.json()
            if res.get('ret') == 0:
                img = res.get('data').get('image')
                response = make_response(jsonify(result=img))
                response.headers['Access-Control-Allow-Origin'] = '*'
                response.headers['Access-Control-Allow-Methods'] = 'OPTIONS,HEAD,GET,POST'
                response.headers['Access-Control-Allow-Headers'] = 'x-requested-with'
                return response
            else:
                return jsonify(error=3001, message='美妆参数错误')
        else:
            return jsonify(error=3002, message='美妆请求失败')


parser4 = reqparse.RequestParser()
parser4.add_argument('image_id', type=str, required=True)  # 融合文件


class DetectImageApi(Resource):
    """ 上传图片API """

    @staticmethod
    def post():
        args = parser4.parse_args()
        image_id = args.get('image_id', '')
        image_url = urljoin(current_app.config['CDN_URL'], image_id)

        # 腾讯云API鉴权
        auth = Auth(current_app.config['APP_ID'],
                    current_app.config['SECRET_ID'],
                    current_app.config['SECRET_KEY'])
        sig = auth.get_sign('', 1200)

        # 获取人脸检测结果
        url = 'http://recognition.image.myqcloud.com/face/detect'
        detect_res = requests.post(url,
                                   json=dict(appid=current_app.config['APP_ID'],
                                             mode=1,
                                             url=image_url),
                                   headers={'Authorization': sig})
        if detect_res.status_code == 200:
            detect_res = detect_res.json()
            if detect_res.get('code') == 0:
                try:
                    gender = detect_res['data']['face'][0]['gender']
                    gender = int(round(gender / 100))
                except (KeyError, IndexError):
                    gender = None
                return jsonify(gender=gender)
            return jsonify(error=4002, message='检测不到人脸')
        return jsonify(error=4001, message='接口请求异常')


api.add_resource(UploadImageApi, '/solar-merge-face/api/upload-image')
api.add_resource(MergeFaceApi, '/solar-merge-face/api/merge-face')
api.add_resource(TestApi, '/api/test')
api.add_resource(DetectImageApi, '/solar-merge-face/api/detect-image')
